package com.sky.aspect;

import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.apache.xmlbeans.impl.xb.xsdschema.LocalComplexType;
import org.apache.xmlbeans.impl.xb.xsdschema.Public;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MemberSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * 自动拦截切面类（切入点+通知）
 */

//功能场景：业务操作本质上都是对数据库增删改查，这些增删改查操作其实存在一些共同点
//例子：当执行insert插入操作时，creat_time 是当前系统时间，creat_user 都是当前线程ThreadLocal区存的用户id
//每一个insert相关的业务操作都包含这个修改，可以集中处理
//基础工具：自定义注解 AutoFill,将对数据库的操作进行切面，识别拦截，加在mapper层
//技术点：枚举、自定义注解、AOP切面、反射赋值
//(1)自定义注解,创建java.com.sky.annotation注解 包
//(2)创建java.com.sky.aspect切面 包

@Aspect//切面类
@Component//作为一个bean，需要交给spring容器管理
@Slf4j//记录日志
public class AutoFillAspect {

    /**
     * 切入点，切点表达式
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")//指定需要进行拦截的类方法
    //第一个* 表示任意返回值，后面两个是模糊匹配，(..)表示匹配到所有的参数类型
    // 既是mapper包下的方法类，同时方法加上了AutoFill注解，就会被拦截
    public void autoFillPointCut(){}

    /**
     * 前置通知，也就是在执行拦截方法之前进行操作
     */
    @Before("autoFillPointCut()")//前置通知标志,指定了切入点
    public void autoFill(JoinPoint joinPoint){
        log.info("开始公共字段自动填充...");


        //数据库操作类型，反射包的代码（从切入点到原始数据库操作）
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();//方法签名
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//签名中的注解
        OperationType operationType = autoFill.value();//获得注解的参数（枚举类的一个对象）

        //数据库实体对象,
        Object[] args = joinPoint.getArgs();
        if(args == null || args.length == 0) return;

        Object entity = args[0];//这要求我们把实体表放到第一个参数

        //准备赋值数据
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();//ThreadLocal线程局部变量

        //通过反射赋值
        //反射是一个相当复杂的东西，今天很难有机会弄懂了

        if(operationType==OperationType.INSERT){

            try {

                //方法名setCreateTime等不要写错
                Method setCreateTime =  entity.getClass().getDeclaredMethod("setCreateTime",LocalDateTime.class);
                Method setCreateUser =  entity.getClass().getDeclaredMethod("setCreateUser",Long.class);
                Method setUpdateTime =  entity.getClass().getDeclaredMethod("setUpdateTime",LocalDateTime.class);
                //Method setUpdateUser =  entity.getClass().getDeclaredMethod("setUpdateUser",Long.class);
                Method setUpdateUser =  entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER,Long.class);

                setCreateTime.invoke(entity,now);
                setUpdateTime.invoke(entity,now);
                setCreateUser.invoke(entity,currentId);
                setUpdateUser.invoke(entity,currentId);

            } catch (Exception e) {
                e.printStackTrace();
            }
            //try-catch 是 Java 和许多其他编程语言中用于异常处理的关键字组合，它允许程序员优雅地捕获和处理运行时可能出现的错误
            //在Java中，可以使用多重 catch 子句来捕获不同类型的异常。这样，可以根据异常的不同类型执行不同的处理逻辑

        }else if(operationType==OperationType.UPDATE){
            try {

                Method setUpdateTime =  entity.getClass().getDeclaredMethod("setUpdateTime",LocalDateTime.class);
                Method setUpdateUser =  entity.getClass().getDeclaredMethod("setUpdateUser",Long.class);
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);

            } catch (Exception e) {
                e.printStackTrace();
            }
        }else  return;


    }//参数是切入点

}
